package biz

import (
	"context"
	ke "github.com/go-kratos/kratos/v2/errors"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/zegoim/zego_server_assistant/token/go/src/token04"
	v1 "paint-guess/api/paint_guess/v1"
	"paint-guess/internal/utils"
	"strconv"
)

const (
	HOMEOWNER = 0
	MEMBER    = 1
)

// 维护用户-房间信息，多对一
type UserRoom struct {
	// 唯一标识
	Id int64 `gorm:"primaryKey"`
	// 房间号
	RoomId string
	// 用户唯一标识
	UserId string
	// 用户处于当前房间的身份
	Identity uint32
	// 昵称
	Nickname string

	Score int64
}

func (UserRoom) TableName() string {
	return "user_room"
}

type Room struct {
	//
	Id       int64
	RoomId   string
	Password string
}

func (Room) TableName() string {
	return "room"
}

type PaintGuessRepo interface {
	GetCountByRoomId(ctx context.Context, roomId string) (int64, error)
	InsertUserRoom(ctx context.Context, roomId, userId, nickname string, identity uint32) error
	DeleteUserRoom(ctx context.Context, userId string) (int64, error)
	GetUserRoomMsgByUserId(ctx context.Context, userId string) (*UserRoom, error)
	GetUserRoomMsgByRoomId(ctx context.Context, roomId string) ([]UserRoom, error)
	UpdateUserScore(ctx context.Context, userId string, score int64) (int64, error)

	InsertRoom(ctx context.Context, roomId, password string) error
	GetRoomByRoomId(ctx context.Context, roomId string) (*Room, error)
	DeleteRoomByRoomId(ctx context.Context, roomId string) (int64, error)
}

type PaintGuessUsecase struct {
	repo PaintGuessRepo
	log  *log.Helper
}

func NewPaintGuessUsecase(repo PaintGuessRepo, logger log.Logger) *PaintGuessUsecase {
	return &PaintGuessUsecase{repo: repo, log: log.NewHelper(logger)}
}

func (uc *PaintGuessUsecase) GetRoomPersonNum(ctx context.Context, req *v1.GetRoomPersonNumRequest) (*v1.GetRoomPersonNumReply, error) {
	_, err := strconv.ParseInt(req.RoomId, 10, 64)
	if err != nil {
		uc.log.Error("GetRoomPersonNum ParseInt: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "error:"+err.Error())
	}
	num, err := uc.repo.GetCountByRoomId(ctx, req.RoomId)
	if err != nil {
		uc.log.Error("GetRoomPersonNum GetCountByRoomId: %v, error: %v", req.RoomId, err.Error())
	}
	return &v1.GetRoomPersonNumReply{PersonNum: num}, nil
}

// TODO 创建房间和加入房间必须同时成功 加入事务
func (uc *PaintGuessUsecase) JoinRoom(ctx context.Context, req *v1.JoinRoomRequest) (*v1.JoinRoomReply, error) {
	// 1. 判断房间是否已经存在
	_, err := strconv.ParseInt(req.RoomId, 10, 64)
	if err != nil {
		uc.log.Error("JoinRoom ParseInt: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "error:"+err.Error())
	}
	num, err := uc.repo.GetCountByRoomId(ctx, req.RoomId)
	if err != nil {
		uc.log.Error("JoinRoom GetCountByRoomId: %v, error: %v", req.RoomId, err.Error())
	}
	// 雪花算法生成id
	userId := utils.NextId()
	if num == 0 {
		//if req.UserId == "15" {
		//	fmt.Println("用户 " + req.NickName + " 发现房间不存在，并等待")
		//	time.Sleep(10 * time.Second)
		//}
		//fmt.Println("用户 " + req.NickName + " 开始创建房间")
		// 房间不存在，创建房间并设置房间密码，多人同时创建一个房间，mysql room表格房间唯一，因此不会导致错误
		err = uc.repo.InsertRoom(ctx, req.RoomId, req.Password)
		if err != nil {
			uc.log.Error("JoinRoom InsertRoom: %v error: %v", req.RoomId, err.Error())
			return &v1.JoinRoomReply{Ok: false, Message: "服务异常，请稍后重试"}, err
		}
		// 加入房间
		err = uc.repo.InsertUserRoom(ctx, req.RoomId, userId, req.NickName, HOMEOWNER)
		if err != nil {
			return &v1.JoinRoomReply{Ok: false, Message: "服务异常，请稍后重试"}, err
		}
	} else {
		// 房间已经存在，加入房间
		// 1. 判断密码是否正确
		room, err := uc.repo.GetRoomByRoomId(ctx, req.RoomId)
		if err != nil {
			return &v1.JoinRoomReply{Ok: false, Message: "服务异常，请稍后重试"}, err
		}
		if room.Password != req.Password {
			return &v1.JoinRoomReply{Ok: false, Message: "房间密码错误"}, nil
		}
		// 2. 加入房间
		err = uc.repo.InsertUserRoom(ctx, req.RoomId, userId, req.NickName, MEMBER)
		if err != nil {
			return &v1.JoinRoomReply{Ok: false, Message: "服务异常，请稍后重试"}, err
		}
	}
	return &v1.JoinRoomReply{Ok: true, Message: "success", UserId: userId}, nil

}

// 退出房间，若房间没人了则销毁房间
// TODO 如果退出的同时有人进来, 会出现没有房间，但人还在房间里的情况
// TODO 接入事务
func (uc *PaintGuessUsecase) ExitRoom(ctx context.Context, req *v1.ExitRoomRequest) (*v1.ExitRoomReply, error) {
	// 根据userId获取房间号
	userRoom, err := uc.repo.GetUserRoomMsgByUserId(ctx, req.UserId)
	if userRoom == nil {
		return &v1.ExitRoomReply{Ok: false}, ke.New(400, "error", "用户不存在")
	}
	// 删除房间和用户对应信息
	_, err = uc.repo.DeleteUserRoom(ctx, req.UserId)
	if err != nil {
		return &v1.ExitRoomReply{Ok: false}, err
	}
	// 判断是否是最后一个人,即房间是否还有人
	if err != nil {
		uc.log.Error("ExitRoom ParseInt: %v, error: %v", userRoom.RoomId, err.Error())
		return nil, ke.New(500, "server error", "")
	}
	num, err := uc.repo.GetCountByRoomId(ctx, userRoom.RoomId)
	if err != nil {
		uc.log.Error("ExitRoom GetCountByRoomId: %v, error: %v", userRoom.RoomId, err.Error())
		return nil, ke.New(500, "server error", "")
	}
	if num == 0 {
		// 房间没人了，删除房间
		_, err := uc.repo.DeleteRoomByRoomId(ctx, userRoom.RoomId)
		if err != nil {
			uc.log.Error("ExitRoom DeleteRoomByRoomId: %v, error: %v", userRoom.RoomId, err.Error())
			return nil, ke.New(500, "server error", "")
		}
	}
	// 房间还有人，不进行操作
	return &v1.ExitRoomReply{Ok: true}, nil
}

func (uc *PaintGuessUsecase) GetToken(ctx context.Context, req *v1.GetTokenRequest) (*v1.GetTokenReply, error) {
	// 1. 判断roomId、userId、password、nickname是否能够与已进入房间用户能够对应上
	// 		避免用户直接调用获取token
	room, err := uc.repo.GetRoomByRoomId(ctx, req.RoomId)
	if err != nil {
		uc.log.Error("GetToken GetRoomByRoomId: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "")
	}
	if req.Password != room.Password {
		return nil, ke.New(499, "参数错误", "用户未进入房间")
	}
	// 2. 用户对应不上，返回错误
	userRoom, err := uc.repo.GetUserRoomMsgByUserId(ctx, req.UserId)
	if err != nil {
		uc.log.Error("GetToken GetUserRoomMsgByUserId: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "")
	}
	if req.UserId != userRoom.UserId || req.RoomId != userRoom.RoomId || req.NickName != userRoom.Nickname {
		return nil, ke.New(499, "参数错误", "用户未进入房间")
	}
	// 3. 用户在房间中，生成token返回
	token, err := generateToken(req.UserId)
	if err != nil {
		uc.log.Error("GetToken generateToken: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "")
	}
	return &v1.GetTokenReply{Ok: true, Message: "success", Token: token}, nil
}

func generateToken(userId string) (string, error) {
	var appId uint32 = 1947182089                      // Zego派发的数字ID, 各个开发者的唯一标识
	serverSecret := "b433b1428e7c104585bba0e44fad5422" // 在获取 token 时进行 AES 加密的密钥
	var effectiveTimeInSeconds int64 = 3600            // token 的有效时长，单位：秒
	var payload string = ""                            // token业务认证扩展，基础鉴权token此处填空

	//生成token
	token, err := token04.GenerateToken04(appId, userId, serverSecret, effectiveTimeInSeconds, payload)
	if err != nil {
		return "", err
	}
	return token, nil
}

func (uc *PaintGuessUsecase) GetRoomUserMsg(ctx context.Context, req *v1.GetRoomUserMsgRequest) (*v1.GetRoomUserMsgReply, error) {
	_, err := strconv.ParseInt(req.RoomId, 10, 64)
	if err != nil {
		uc.log.Error("GetRoomUserMsg ParseInt: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "error:"+err.Error())
	}
	list, err := uc.repo.GetUserRoomMsgByRoomId(ctx, req.RoomId)
	if err != nil {
		uc.log.Error("GetRoomUserMsg GetUserRoomMsgByRoomId: %v, error: %v", req.RoomId, err.Error())
		return nil, ke.New(500, "server error", "error:"+err.Error())
	}
	if len(list) == 0 {
		return nil, nil
	}
	// TODO 数组怎么赋值
	var res v1.GetRoomUserMsgReply
	res.UserMsg = make([]*v1.GetRoomUserMsgReply_UserMsg, len(list))
	for i, userMsg := range list {
		res.UserMsg[i] = &v1.GetRoomUserMsgReply_UserMsg{
			UserId:   userMsg.UserId,
			Nickname: userMsg.Nickname,
			Identity: userMsg.Identity,
			Score:    userMsg.Score,
		}
	}
	return &res, nil
}

func (uc *PaintGuessUsecase) UpdateUserScore(ctx context.Context, req *v1.UpdateScoreRequest) (*v1.UpdateScoreReply, error) {
	count, err := uc.repo.UpdateUserScore(ctx, req.UserId, req.Score)
	if err != nil {
		uc.log.Error("UpdateUserScore UpdateUserScore: ", req.Score, req.UserId, err.Error())
		return nil, ke.New(500, "server error", "error:"+err.Error())
	}
	ok := count > 0
	return &v1.UpdateScoreReply{
		Ok:      ok,
		Message: "共" + strconv.FormatInt(count, 10) + "条数据收到影响",
	}, nil
}
